using System;
using System.Collections.Generic;
using System.Text;
using System.Security.Principal;
using System.Runtime.InteropServices;
using System.Security.Permissions;
using System.Security;
using System.Runtime.CompilerServices;
using System.Threading;

namespace InfiniTec.Security
{
    /// <summary>
    /// Represents a <see cref="Username"/> and <see cref="Password"/> combination.
    /// </summary>
    public class CredentialEntry: IDisposable
    {
        private object _SyncRoot = new object();

        UserToken _Handle;
        string _Username;
        string _Domain;
        SecureString _Password;

        private LogonType _LogonType;

        /// <summary>
        /// Gets or sets the <see cref="LogonType"/> to use.
        /// </summary>
        public LogonType LogonType
        {
            get { return _LogonType; }
            set { _LogonType = value; }
        }


        private DateTime _Created;

        /// <summary>
        /// Returns the <see cref="DateTime"/> when the object was created.
        /// </summary>
        public DateTime Created
        {
            get { return _Created; }
        }

        /// <summary>
        /// Gets or sets the username for this <see cref="CredentialEntry"/>
        /// </summary>
        public string Username
        {
            get { _LastAccessed = DateTime.Now; return _Username; }
            set { _Username = value; }
        }
        /// <summary>
        /// Gets or sets the domain for this <see cref="CredentialEntry"/>
        /// </summary>
        public string Domain
        {
            get { _LastAccessed = DateTime.Now; return _Domain; }
            set { _Domain = value; }
        }

        /// <summary>
        /// Gets or sets the password for this <see cref="CredentialEntry"/>
        /// </summary>
        public SecureString Password
        {
            get { _LastAccessed = DateTime.Now; return _Password; }
            set { _Password = value; }
        }

        private DateTime _LastAccessed;
           
        /// <summary>
        /// Returns the <see cref="DateTime"/> when the object was last accessed.
        /// </summary>
        public DateTime LastAccessed
        {
            get { return _LastAccessed; }
        }

        /// <summary>
        /// Returns the <see cref="WindowsIdentity"/> represented by this instance.
        /// </summary>
        public WindowsIdentity Identity
        {
            get { _LastAccessed = DateTime.Now; return GetWindowsIdentity(); }
        }

        /// <summary>
        /// Creates a new instance of this class and logs on as the specified user.
        /// </summary>
        /// <param name="username">The name of the user to log on to (either as 'username' or as 'domain\username')</param>
        /// <param name="password">The password of the useraccount</param>
        /// <param name="logonType">The <see cref="LogonType"/> to use.</param>
        public CredentialEntry(string username, SecureString password, LogonType logonType)
        {
            string[] parts;

            if (string.IsNullOrEmpty(username)) throw new ArgumentException("Username must not be null or empty", "username");

            _Created = DateTime.Now;
            _LogonType = logonType;

            parts = username.Split('\\');
            if (parts.Length == 2)
            {
                _Domain = parts[0];
                _Username = parts[1];
            }
            else _Username = username;

            _Password = password;
        }

        private WindowsIdentity GetWindowsIdentity()
        {
            bool result;
            LogonProvider provider;
            IntPtr passwordPointer = IntPtr.Zero;

            lock (_SyncRoot)
            {
                RuntimeHelpers.PrepareConstrainedRegions();
                try
                {
                    passwordPointer = Marshal.SecureStringToGlobalAllocUnicode(_Password);

                    provider = (LogonType == LogonType.NewCredentials) ? LogonProvider.WinNT50 : LogonProvider.Default;

                    result = NativeMethods.LogonUser(_Username, _Domain, passwordPointer, LogonType, provider, out _Handle);
                    
                    if (!result)
                    {
                        throw Marshal.GetExceptionForHR(Marshal.GetLastWin32Error());
                    }
                }
                finally
                {
                    if (passwordPointer != IntPtr.Zero) Marshal.ZeroFreeGlobalAllocUnicode(passwordPointer);
                }
            }

            return _Handle.GetWindowsIdentity();
        }

        #region IDisposable Members

        /// <summary>
        /// Releases all resources associated with this instance
        /// </summary>
        [SecurityPermission(SecurityAction.Demand, UnmanagedCode = true)]
        public void Dispose()
        {
            Dispose(true);
            GC.SuppressFinalize(this);
        }

        private void Dispose(bool disposing)
        {
            UserToken token;

            if (!disposing) return;

            token = Interlocked.Exchange<UserToken>(ref _Handle, null);
            if (token != null) token.Dispose();
        }

        #endregion
    }
}
